import {Channel} from "amqplib";
import {TournamentConfig} from "../../match/Tournament"
import {BattleRoom} from "../../match/doudizhu/TournamentRoom"
import {createLock, unlocker} from "../../utils/lock"
import {AsyncRedisClient} from "../../utils/redis"
import {ISocketPlayer} from "../ISocketPlayer"
import {shuffle} from 'lodash'
import {logger} from "../../utils/logger";
import * as uuid from 'node-uuid'

const tournamentConfig: TournamentConfig = {
  juShu: 2,
  queueLimit: 8,
  playerCounter: 4,
  nPlayersToKnockOut: 4,
  nPlayersToEnd: 4,
  entryFee: 1,
  gameType: 'zhadan',
  _id: 'random',
  rule: {
    "ruleType": 4,
    "playerCount": 4,
    "juShu": 4,
    "useJoker": true,
    "quanJiang": true,
    "shaoJi": false,
    rewards: [10, 5],
    roomType: BattleRoom.RoomType
  }
}

const battleTournamentConfig: TournamentConfig = {
  queueLimit: 2,
  juShu: 1,
  playerCounter: 2,
  nPlayersToKnockOut: 2,
  nPlayersToEnd: 2,
  entryFee: 0,
  gameType: 'doudizhu',
  _id: 'battle',
  rule: {
    _id: 'battle',
    "ruleType": 4,
    "playerCount": 2,
    "juShu": 1,
    roomType: BattleRoom.RoomType,
    rewards: [3], autoCommit: 30
  }
}
const tournamentLobbyQueue = `t:lobbyQueue`

export const tournamentConfigs: TournamentConfig[] = [
  battleTournamentConfig
]

type  TournamentRequest = { tId: string, gameType?: string }


export class TornamentManager {
  private readonly lock: (lockId: string, timeOutInMs?: number) => Promise<unlocker>;

  constructor(readonly redisClient: AsyncRedisClient,
              readonly channel: Channel,
              readonly queueNamePrefix: string = tournamentLobbyQueue) {
    this.lock = createLock(redisClient)
  }


  gameTypeToTourQueue(gameType: string, tId: string) {
    return `${this.queueNamePrefix}:${gameType}:${tId}`
  }

  async requestStartBattle(config: TournamentConfig) {
    const tournamentId = `${Date.now()}`

    const queueName = this.gameTypeToTourQueue(config.gameType, config._id)
    const playersInTour = shuffle(await this.redisClient.spopAsync(queueName, config.playerCounter))
    this.channel.sendToQueue(`${config.gameType}Tournament`, new Buffer(JSON.stringify({
      name: "startBattle", payload: {
        tournamentId,
        players: playersInTour,
        config: config
      }
    })))

    const multi = this.redisClient.batch()
    playersInTour.forEach(pId => {
      multi.hdel(`u:${pId}`, `t:${config.gameType}`, config._id)
    })
    multi.exec_atomic()
  }


  async putRobotToTornamentQueue(gameType: string, config: TournamentConfig) {
    const queueName = this.gameTypeToTourQueue(gameType, config._id)

    const unlock = await this.lock(queueName, 3000)

    try {
      const inQueue = await this.redisClient.scardAsync(queueName)

      if (inQueue === 1) {
        await this.addRobot(config)
        await this.requestStartBattle(config)
      }
    } catch (e) {

    } finally {
      unlock()
    }
  }

  private addRobot(config: TournamentConfig) {
    return this.redisClient.saddAsync(
      this.gameTypeToTourQueue(config.gameType, config._id),
      `robot${uuid.v4()}`)
  }

}


export function createHandler(redisClient: AsyncRedisClient, queueNamePrefix: string = tournamentLobbyQueue) {

  const lock = createLock(redisClient)
  
  function gameTypeToTourQueue(gameType: string, tId: string) {
    return `${queueNamePrefix}:${gameType}:${tId}`
  }

  function getCurrentPlayerNum(queueLimit, playerCounter, currentPlayer) {
    let rate = queueLimit / playerCounter
    let result = Math.floor(currentPlayer / rate)
    if (result === 0 && currentPlayer > 0) {
      result = 1
    }
    return result;
  }

  const handlers = {

    queueName: queueNamePrefix,

    'tournament/tournaments': async (player: ISocketPlayer, {gameType}: TournamentRequest) => {
      let tournamentInfo = []

      const batch = redisClient.batch()
      tournamentConfigs.forEach(config => {
        batch.get(`tc:${config._id}`)
      })

      const tourRoomCounters = await batch.execAsync()

      for (let i = 0; i < tournamentConfigs.length; i++) {
        const roomCount = Number(tourRoomCounters[i])
        const info = {
          _id: tournamentConfigs[i]._id,
          playerCount: 20 + Math.floor(Math.random() * 2) * 4 + roomCount * 4,
          gameType: tournamentConfigs[i].gameType,
          entryFee: tournamentConfigs[i].entryFee
        }
        tournamentInfo.push(info);
      }
      player.sendMessage('tournament/tournamentsReply', {tournaments: tournamentInfo})
    },

    'tournament/myTourId': async (player: ISocketPlayer, {gameType}: TournamentRequest) => {
      const tId = await redisClient.hgetAsync(`u:${player._id}`, `t:${gameType}`)
      player.sendMessage('tournament/myTourIdReply', {currentId: tId})
    },

    'tournament/list': async (player: ISocketPlayer, {gameType, tId = 'battle'}: TournamentRequest) => {

      const config = tournamentConfigs.find(c => c._id === tId)

      if (!config) {
        return
      }

      const q = gameTypeToTourQueue(gameType, tId)
      const [nPlayersInQueue, isInQueue] = await redisClient.batch().scard(q)
        .sismember(q, player._id)
        .execAsync()
      let {queueLimit, playerCounter} = config;
      const currentPlayers = getCurrentPlayerNum(queueLimit, playerCounter, nPlayersInQueue)
      player.sendMessage('tournament/queue', {
        currentPlayers: currentPlayers,
        tournamentSize: config.playerCounter, config: config,
        isInQueue
      })
    },

    'tournament/join': async (player: ISocketPlayer, {tId = 'battle'}: TournamentRequest) => {
      try {


        async function isPlayerAlreadyInQueue(player: ISocketPlayer, queue: string) {
          return redisClient.sismemberAsync(queue, player._id)
        }

        async function joinQueue(player: ISocketPlayer, gameType: string, config: TournamentConfig) {


          const [ignore, nPlayersInQueue] = await redisClient.multi()
            .sadd(queueName, player._id)
            .scard(queueName)
            .hset(`u:${player._id}`, `t:${gameType}`, config._id)
            .execAsync()

          return nPlayersInQueue
        }

        const config = tournamentConfigs.find(c => c._id === tId)
        if (!config) {
          return player.sendMessage('tournament/joinReply', {ok: false, info: '没有此比赛Id'})
        }
        const gameType = config.gameType
        const currentTid = await redisClient.hgetAsync(`u:${player._id}`, `t:${gameType}`)

        if (currentTid && currentTid !== tId) {
          return player.sendMessage('tournament/joinReply', {ok: false, info: `你已经在另外一个比赛场`})
        }

        const queueName = gameTypeToTourQueue(gameType, config._id)

        const unlock = await lock(queueName, 3000)

        try {

          const alreadyInQueue = await isPlayerAlreadyInQueue(player, queueName)

          if (alreadyInQueue) {
            player.sendMessage('tournament/joinReply', {ok: true, info: '加入比赛成功'})
            return
          }

          const nPlayersInQueue = await joinQueue(player, gameType, config)

          let {queueLimit, playerCounter} = config;
          const currentPlayers = getCurrentPlayerNum(queueLimit, playerCounter, nPlayersInQueue)

          player.sendMessage('tournament/wait', {
            currentPlayers: currentPlayers,
            tournamentSize: config.playerCounter, config: config
          })

          player.sendMessage('tournament/joinReply', {ok: true, info: '加入比赛成功'})

          if (nPlayersInQueue >= config.queueLimit) {

            for (let i = 0; i < config.queueLimit / config.playerCounter; i++) {
              const tournamentId = `${Date.now()}`
              const playersInTour = shuffle(await redisClient.spopAsync(queueName, config.playerCounter))
              player.requestTo(`${gameType}Tournament`, "startBattle", {
                tournamentId,
                players: playersInTour,
                config: config
              })

              const multi = redisClient.batch()
              playersInTour.forEach(pId => {
                multi.hdel(`u:${pId}`, `t:${gameType}`, config._id)
              })
              multi.exec_atomic()
            }
          }
        } catch (e) {
          logger.error({message: 'tournament/join', error: e.message, stack: e.stack})
        } finally {
          unlock()
        }
      } catch (e) {
        player.sendMessage('tournament/joinReply', {ok: false, info: '加入失败 稍后重试'})
      }
    },

    'tournament/quit': async (player: ISocketPlayer, {gameType, tId = 'battle'}: TournamentRequest) => {

      const queueName = gameTypeToTourQueue(gameType, tId)
      try {
        const unlock = await lock(queueName, 3000)
        try {
          const exists = await redisClient.sismemberAsync(queueName, player._id)
          if (exists) {

            await redisClient.multi()
              .srem(queueName, player._id)
              .hdel(`u:${player._id}`, `t:${gameType}`, tId)
              .execAsync()

            return player.sendMessage('tournament/quitReply', {ok: true, info: '已退出比赛'})
          } else {
            return player.sendMessage('tournament/quitReply', {ok: false, info: '比赛已经开始或者还没有加入比赛'})
          }
        } catch (e) {
        } finally {
          unlock()
        }
      } catch (e) {
        player.sendMessage('tournament/quitReply', {ok: false, info: '退出失败 稍后重试'})
      }
    }
  }

  return handlers
}



